// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;

using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Server;


namespace Microsoft.TeamFoundation.Migration.Toolkit.Wit
{
    /// <summary>
    /// TFS work item.
    /// </summary>
    public class TfsMigrationWorkItem : IMigrationWorkItem
    {
        private TfsCore m_core;                             // TFS shared core
        private Watermark m_watermark;                      // Watermark
        private string m_workItemType;                      // Name of the work item type
        private MigrationWorkItemData m_flags;              // Flags
        private WorkItem m_workItem;                        // Work item

        /// <summary>
        /// Returns underlying work item from the WIT OM.
        /// </summary>
        public WorkItem WorkItem { get { return m_workItem; } }

        /// <summary>
        /// Returns work item's URI.
        /// </summary>
        public string Uri { get { return m_workItem.Uri.ToString(); } }

        /// <summary>
        /// Returns id of the work item.
        /// </summary>
        public Watermark Watermark { get { return m_watermark; } }

        /// <summary>
        /// Gets name of the work item type.
        /// </summary>
        public string WorkItemType { get { return m_workItemType; } }

        /// <summary>
        /// Tells the engine what kind of data the work item has.
        /// </summary>
        public MigrationWorkItemData Flags { get { return m_flags; } }

        /// <summary>
        /// Gets revisions beginning with revision with the given number.
        /// </summary>
        /// <param name="revision">Revision number</param>
        /// <returns>Revisions</returns>
        public ReadOnlyCollection<MigrationRevision> GetRevisions(
            int revision)
        {
            WorkItem item = m_workItem;
            List<MigrationRevision> revisions = new List<MigrationRevision>();

            // Skip previous revisions
            int i = 0;
            while (i < item.Revisions.Count)
            {
                if ((int)item.Revisions[i].Fields[CoreField.Rev].Value >= revision)
                {
                    break;
                }
                else
                {
                    i++;
                }
            }
            for (; i < item.Revisions.Count; i++)
            {
                Revision tfsRev = item.Revisions[i];
                string author = (string)tfsRev.Fields[CoreField.ChangedBy].Value;
                if (string.IsNullOrEmpty(author))
                {
                    author = (string)tfsRev.Fields[CoreField.AuthorizedAs].Value;
                }

                MigrationRevision rev = new MigrationRevision(
                    (int)tfsRev.Fields[CoreField.Rev].Value,
                    author,
                    (DateTime)tfsRev.Fields[CoreField.ChangedDate].Value);

                // Copy fields from this revision
                for (int j = 0; j < tfsRev.Fields.Count; j++)
                {
                    Field tfsField = tfsRev.Fields[j];
                    if (MustTakeField(tfsField.FieldDefinition))
                    {
                        // Translate field's value
                        string name = m_core.Config.FieldForm == TfsFieldForm.Reference ? tfsField.ReferenceName : tfsField.Name;
                        object v = TranslateFieldValue(tfsRev.Fields, tfsField);
                        MigrationField f = new MigrationField(name, v);
                        rev.Fields.Add(f);
                    }
                }
                revisions.Add(rev);
            }
            return revisions.AsReadOnly();
        }

        /// <summary>
        /// Gets the most recent values of the given fields.
        /// </summary>
        /// <param name="fieldNames">Names of fields</param>
        /// <returns>Values</returns>
        public IEnumerable<MigrationField> GetLatestValues(
            IEnumerable<string> fieldNames)
        {
            if (fieldNames == null)
            {
                throw new ArgumentNullException("fieldNames");
            }

            foreach (string fieldName in fieldNames)
            {
                Field f = m_workItem.Fields[fieldName];
                object value = TranslateFieldValue(m_workItem.Fields, f);
                yield return new MigrationField(fieldName, value);
            }
        }
        
        /// <summary>
        /// Gets a list of files attached to a work item.
        /// </summary>
        /// <returns>List of attachments.</returns>
        public ReadOnlyCollection<IMigrationFileAttachment> GetFileAttachments()
        {
            WorkItem item = m_workItem;
            List<IMigrationFileAttachment> files = new List<IMigrationFileAttachment>();
            string tempDir = m_core.Config.Session.Policies.AttachmentsConflict.TempDirectory;

            foreach (Attachment attach in item.Attachments)
            {

                files.Add(new TfsMigrationFileAttachment(attach, tempDir));
            }
            return files.AsReadOnly();
        }

        /// <summary>
        /// Customizes change list on the target side before it gets translated.
        /// </summary>
        /// <param name="target">Change list from the opposite side</param>
        public void OnTargetUpdate(
            IWorkItemUpdatePackage target)
        {
            // This method does nothing.
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="core">Shared TFS core</param>
        /// <param name="item">TFS work item</param>
        /// <param name="fieldForm">Tells what field name should be used</param>
        internal TfsMigrationWorkItem(
            TfsCore core,
            WorkItem item)
        {
            m_core = core;
            m_watermark = new Watermark(
                Convert.ToString(item.Id, CultureInfo.InvariantCulture),
                item.Rev);
            m_workItemType = (string)item.Fields[CoreField.WorkItemType].Value;
            if ((int)item[CoreField.AttachedFileCount] > 0)
            {
                m_flags |= MigrationWorkItemData.Attachments;
            }
            int linkCount = (int)item[CoreField.ExternalLinkCount] + (int)item[CoreField.RelatedLinkCount] + (int)item[CoreField.HyperLinkCount];
            if (linkCount > 0)
            {
                m_flags |= MigrationWorkItemData.Links;
            }
            m_workItem = item;
        }

        /// <summary>
        /// Determines whether given field should be included into revision.
        /// </summary>
        /// <param name="def">Field definition</param>
        /// <returns>true if the field should be included</returns>
        private static bool MustTakeField(
            FieldDefinition def)
        {
            switch (def.Id)
            {
                case (int)CoreField.AreaPath:
                case (int)CoreField.IterationPath:
                    return true;

                case (int)CoreField.WorkItemType:
                case (int)CoreField.IterationId:
                case (int)CoreField.AreaId:
                case (int)CoreField.AuthorizedAs:
                case (int)CoreField.ChangedBy:
                case (int)CoreField.ChangedDate:
                case (int)CoreField.CreatedDate:
                case (int)CoreField.CreatedBy:
                    return false;
                default:
                    return !def.IsComputed;
            }
        }

        /// <summary>
        /// Translates field value.
        /// </summary>
        /// <param name="fields">Fields collection</param>
        /// <param name="fieldName">Field whose value should be translated</param>
        /// <returns>Translated field value</returns>
        private object TranslateFieldValue(
            FieldCollection fields,
            Field field)
        {
            object value = field.Value;

            // Do mandatory translations
            switch (field.Id)
            {
                case (int)CoreField.AreaPath:
                case (int)CoreField.IterationPath:
                    string path = value as string;
                    Debug.Assert(path != null, "Path is not string!");

                    // Remove first part (which is project name)
                    int index = path.IndexOf('\\');
                    value = index == -1 ? string.Empty : path.Substring(index + 1);
                    break;

                case (int)CoreField.CreatedDate:
                case (int)CoreField.ChangedDate:
                    value = ((DateTime)value).ToUniversalTime();
                    break;

                default:
                    break;
            }

            return m_core.Config.Session.FieldValueTransformer.GetTransformedValue(fields, field, value);
        }
    }
}
